package com.tool.engine.export.policy;

import com.tool.engine.config.AdjusterProperties;
import com.tool.engine.export.constant.CollectTypeEnum;
import com.tool.engine.export.mertics.Collector;
import com.tool.engine.export.mertics.collect.NumberRecord;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

@Data
@Slf4j
public class DynamicThresholdAdjuster implements AdjusterPolicy {

    private AdjusterProperties adjusterProperties;

    private final double memoryWeight;    // 内存影响权重 (0.0-1.0)
    private final double timeWeight;      // 时间影响权重 (0.0-1.0)
    private final int minThreshold;       // 最小阈值
    private final int maxThreshold;       // 最大阈值
    private final int initialThreshold;
    private final double adjustmentFactor;// 平滑调整系数

    private final double fetchTimeWeight;
    private final double writeTimeWeight;
    private final double totalTimeWeight;

    // 状态记录
    private final AtomicInteger currentThreshold;
    private final ReentrantLock adjustmentLock = new ReentrantLock();

    private final Collector collector;

    private static int period = 60*30;

    private long startSleepTime = 0L;

    private static int recoveryEpoch = 10;

    private static int waitRecoveryEpoch = 10;

    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    private boolean enabled;

    public DynamicThresholdAdjuster(AdjusterProperties adjusterProperties,
                                    Collector collector) {

        this.memoryWeight = adjusterProperties.getMemoryWeight();
        this.timeWeight = adjusterProperties.getTimeWeight();
        // 参数校验
        if (memoryWeight + timeWeight > 1.0) {
            throw new IllegalArgumentException("weigh value sum over 1.0");
        }
        this.currentThreshold = new AtomicInteger(adjusterProperties.getInitialThreshold());
        this.initialThreshold = adjusterProperties.getInitialThreshold();
        this.minThreshold = adjusterProperties.getMinThreshold();
        this.maxThreshold = adjusterProperties.getMaxThreshold();
        this.adjustmentFactor = adjusterProperties.getAdjustmentFactor();
        this.fetchTimeWeight = adjusterProperties.getFetchTimeWeight();
        this.writeTimeWeight = adjusterProperties.getWriteTimeWeight();
        this.totalTimeWeight = adjusterProperties.getTotalTimeWeight();
        this.collector = collector;
        this.enabled = adjusterProperties.isEnabled();
        scheduler.scheduleAtFixedRate(this::adjustThreshold,
                    period,
                    period,
                    TimeUnit.SECONDS);
    }

    @Override
    public void adjustThreshold() {
        if(!enabled) {
            log.info("metrics:{}",collector.toString());
            return;
        }
        adjustmentLock.lock();
        try {
            if (collector.getProcessingTaskCount() <= 0) {
                if (startSleepTime > 0) {
                    long diff = System.currentTimeMillis() - startSleepTime;
                    if (diff >= (long) period * waitRecoveryEpoch * 1000) {
                        int thresholdDiff = (this.initialThreshold-this.currentThreshold.get()) / recoveryEpoch;
                        int newThreshold;
                        if(thresholdDiff == 0){
                            newThreshold =  this.initialThreshold;
                        }else{
                            newThreshold = this.currentThreshold.get() + thresholdDiff;
                        }
                        this.currentThreshold.set(newThreshold);
                        log.info("Adjusting result: not exit processing task start recover to initialThreshold -> Threshold: {}", currentThreshold.get());
                    }
                } else {
                    startSleepTime = System.currentTimeMillis();
                }
                log.info("Adjusting result: not exit processing task -> Threshold: {}", currentThreshold.get());
                return;
            }
            startSleepTime = 0;
            double fetchPressure = calculateTimePressure(collector, CollectTypeEnum.FETCH);
            double writePressure = calculateTimePressure(collector, CollectTypeEnum.WRITE);
            double totalPressure = calculateTimePressure(collector, CollectTypeEnum.TOTAL);
            double memPressure = calculateMemPressure(collector);
            double combinedTimePressure =
                    fetchTimeWeight * fetchPressure +
                            writeTimeWeight * writePressure +
                            totalTimeWeight * totalPressure;
            double reduction = memoryWeight * memPressure
                    + timeWeight * combinedTimePressure;
            int newThreshold = calculateThreshold(memPressure, combinedTimePressure, reduction);
            currentThreshold.set(newThreshold);
            // 打印调试信息
            log.info("Adjusting result: combinedTimePressure:{}, reduction:{} -> Threshold: {}",
                    combinedTimePressure, reduction, newThreshold);
        } catch (Exception e) {
            log.warn("dynamic threshold adjustment failed:{}", e.getMessage());
        } finally {
            adjustmentLock.unlock();
        }
    }

    private int calculateThreshold(double memPressure, double combinedTimePressure, double reduction) {
        int newThreshold;
        if (memPressure <= 0.85 && combinedTimePressure <= 0.7) {
            double recoveryRate = 0.05; // 5%恢复率
            newThreshold = (int) (currentThreshold.get() * (1 + recoveryRate));
            newThreshold = Math.min(maxThreshold, Math.min(maxThreshold, newThreshold));
        } else {
            newThreshold = (int) Math.round(
                    currentThreshold.get() * (1 - adjustmentFactor) +
                            (currentThreshold.get() * (1 - reduction)) * adjustmentFactor
            );
            newThreshold = Math.min(maxThreshold, Math.max(minThreshold, newThreshold));
        }
        return newThreshold;
    }

    @Override
    public int getBathSize() {
        return currentThreshold.get();
    }

    private double calculateTimePressure(Collector collector, CollectTypeEnum typeEnum) {
        List<Long> historyValue = calculateTimeHistory(collector.getCollectValue(typeEnum.getType()));
        double fetchTime = calculateTimePercentile(historyValue, 90);
        double[] fetchThresholds = calculateTimeDynamicThresholds(historyValue);
        double pressure = normalize(fetchTime, fetchThresholds[0], fetchThresholds[1]);
        log.info("Adjusting time: {}, p90:{}, Dynamic:{low:{}, high:{}}, pressure:{}", typeEnum.getDesc(), fetchTime, fetchThresholds[0], fetchThresholds[1], pressure);
        return pressure;
    }

    private double calculateMemPressure(Collector collector) {
        List<Double> historyValue = calculateMemHistory(collector.getCollectValue(CollectTypeEnum.MEMORY_USAGE.getType()));
        double fetchTime = calculateMemPercentile(historyValue, 90);
        double[] fetchThresholds = calculateMemDynamicThresholds(historyValue);
        double pressure = normalize(fetchTime, fetchThresholds[0], fetchThresholds[1]);
        log.info("Adjusting mem: {}, p90:{}, Dynamic:{low:{}, high:{}}, pressure:{}", CollectTypeEnum.MEMORY_USAGE.getDesc(), fetchTime, fetchThresholds[0], fetchThresholds[1], pressure);
        return normalize(fetchTime, fetchThresholds[0], fetchThresholds[1]);
    }

    private List<Long> calculateTimeHistory(List<NumberRecord> values) {
        List<Long> times = new ArrayList<>(values.size());
        for (NumberRecord value : values) {
            times.add(value.getValue().longValue());
        }
        Collections.sort(times);
        return times;
    }

    private List<Double> calculateMemHistory(List<NumberRecord> values) {
        List<Double> times = new ArrayList<>(values.size());
        for (NumberRecord value : values) {
            times.add(value.getValue().doubleValue());
        }

        Collections.sort(times);
        return times;
    }

    // 计算动态压力区间
    private double[] calculateTimeDynamicThresholds(List<Long> values) {
        if (values.isEmpty()) {
            // 默认值 - 根据您的场景调整
            return new double[]{100, 5000};
        }

        int size = values.size();
        long min = values.get(0);
        long p50 = values.get(size / 2);
        long p90 = values.get((int) (size * 0.9));
        long max = values.get(size - 1);

        // 计算区间
        double low = Math.max(min, p50 * 0.7); // 正常值下限
        double high = Math.min(max * 1.1, p90 * 1.5); // 高压值上限

        // 确保合理范围
        if (high < low * 2) high = low * 2;

        return new double[]{low, high};
    }

    // 计算动态压力区间
    private double[] calculateMemDynamicThresholds(List<Double> values) {
        if (values.isEmpty()) {
            // 默认值 - 根据您的场景调整
            return new double[]{100, 5000};
        }

        int size = values.size();
        double min = values.get(0);
        double p50 = values.get(size / 2);
        double p90 = values.get((int) (size * 0.9));
        double max = values.get(size - 1);

        // 计算区间
        double low = Math.max(min, p50 * 0.7); // 正常值下限
        double high = Math.min(max * 1.1, p90 * 1.5); // 高压值上限

        // 确保合理范围
        if (high < low * 2) high = low * 2;

        return new double[]{low, high};
    }

    private double normalize(double value, double low, double high) {
        if (value < low) return 0.0;
        if (value > high) return 1.0;
        BigDecimal diff = BigDecimal.valueOf(value).subtract(BigDecimal.valueOf(low));
        BigDecimal highDiff = BigDecimal.valueOf(high).subtract(BigDecimal.valueOf(low));
        if (highDiff.compareTo(BigDecimal.ZERO) == 0) {
            return 0;
        }
        return diff.divide(highDiff, 2, RoundingMode.HALF_UP).doubleValue();
    }


    private double calculateTimePercentile(List<Long> values, int percentile) {
        if (values == null || values.isEmpty()) {
            log.warn("Records cannot be null or empty");
            return 0;
        }
        if (percentile < 0 || percentile > 100) {
            throw new IllegalArgumentException("Percentile must be between 0 and 100");
        }

        // 2. 计算精确位置（使用线性插值）
        double position = (percentile / 100.0) * (values.size() - 1);

        // 3. 获取下界和上界索引
        int lower = (int) Math.floor(position);
        int upper = (int) Math.ceil(position);

        // 4. 边界检查
        lower = Math.max(0, Math.min(lower, values.size() - 1));
        upper = Math.max(0, Math.min(upper, values.size() - 1));

        // 5. 如果位置正好是整数，直接返回该值
        if (lower == upper) {
            return values.get(lower).doubleValue();
        }

        // 6. 线性插值计算
        double lowerValue = values.get(lower);
        double upperValue = values.get(upper);
        double fraction = position - lower;

        return lowerValue + fraction * (upperValue - lowerValue);
    }

    private double calculateMemPercentile(List<Double> values, int percentile) {
        if (values == null || values.isEmpty()) {
            log.warn("Records cannot be null or empty");
            return 0;
        }
        if (percentile < 0 || percentile > 100) {
            throw new IllegalArgumentException("Percentile must be between 0 and 100");
        }

        // 2. 计算精确位置（使用线性插值）
        double position = (percentile / 100.0) * (values.size() - 1);

        // 3. 获取下界和上界索引
        int lower = (int) Math.floor(position);
        int upper = (int) Math.ceil(position);

        // 4. 边界检查
        lower = Math.max(0, Math.min(lower, values.size() - 1));
        upper = Math.max(0, Math.min(upper, values.size() - 1));

        // 5. 如果位置正好是整数，直接返回该值
        if (lower == upper) {
            return values.get(lower);
        }

        // 6. 线性插值计算
        double lowerValue = values.get(lower);
        double upperValue = values.get(upper);
        double fraction = position - lower;

        return lowerValue + fraction * (upperValue - lowerValue);
    }

}
